#include "net.h"
#include "ip.h"
#include "ethernet.h"
#include "arp.h"
#include "icmp.h"

uint16_t ip_id16 = 0;

/**
 * @brief 处理一个收到的数据包
 * 
 * @param buf 要处理的数据包
 * @param src_mac 源mac地址
 */
void ip_in(buf_t *buf, uint8_t *src_mac)
{
    // TO-DO
    // 如果数据包的长度小于IP头部长度, 丢弃不处理
    if(buf->len < sizeof(ip_hdr_t)) return;

    // 报头检测
    ip_hdr_t* hdr = (ip_hdr_t*)buf->data;
    if(hdr->version != IP_VERSION_4 || swap16(hdr->total_len16) > buf->len) return;

    // 检查校验和字段是否正确
    uint16_t checksum16_bk = hdr->hdr_checksum16;
    hdr->hdr_checksum16 = 0;
    if(checksum16((uint16_t*)hdr, sizeof(ip_hdr_t)) != checksum16_bk) return;
    hdr->hdr_checksum16 = checksum16_bk;

    // 对比目的IP地址是否为本机的IP地址, 如果不是, 则丢弃不处理
    if(memcmp(hdr->dst_ip, net_if_ip, NET_IP_LEN) != 0) return;

    // 去除填充字段 第二个参数非负
    buf_remove_padding(buf, buf->len - swap16(hdr->total_len16));

    // 去除IP报头
    buf_remove_header(buf, sizeof(ip_hdr_t));

    // 向上层传递数据包
    uint8_t src_ip[NET_IP_LEN]; // 源IP 不使用hdr->src_ip以防止后续程序操作buf时修改
    memcpy(src_ip, hdr->src_ip, NET_IP_LEN);
    if(net_in(buf, hdr->protocol, src_ip) == -1) {
        buf_add_header(buf, sizeof(ip_hdr_t));
        icmp_unreachable(buf, src_ip, ICMP_CODE_PROTOCOL_UNREACH);
    }
}

/**
 * @brief 处理一个要发送的ip分片
 * 
 * @param buf 要发送的分片
 * @param ip 目标ip地址
 * @param protocol 上层协议
 * @param id 数据包id
 * @param offset 分片offset，必须被8整除
 * @param mf 分片mf标志，是否有下一个分片
 */
void ip_fragment_out(buf_t *buf, uint8_t *ip, net_protocol_t protocol, int id, uint16_t offset, int mf)
{
    // TO-DO
    // 增加IP数据报头部缓存空间
    buf_add_header(buf, sizeof(ip_hdr_t));

    // 填写IP数据报头部字段
    ip_hdr_t* hdr = (ip_hdr_t*)buf->data;
    hdr->hdr_len = sizeof(ip_hdr_t) / IP_HDR_LEN_PER_BYTE;
    hdr->version = IP_VERSION_4;
    hdr->tos = 0;
    hdr->total_len16 = swap16(buf->len);
    hdr->id16 = swap16(id);
    hdr->flags_fragment16 = swap16(mf * IP_MORE_FRAGMENT + offset / IP_HDR_OFFSET_PER_BYTE);
    hdr->ttl = IP_DEFALUT_TTL;
    hdr->protocol = protocol;
    hdr->hdr_checksum16 = 0;
    memcpy(hdr->src_ip, net_if_ip, NET_IP_LEN);
    memcpy(hdr->dst_ip, ip, NET_IP_LEN);

    // 计算校验和
    hdr->hdr_checksum16 = checksum16((uint16_t*)hdr, sizeof(ip_hdr_t));

    // 发送数据
    arp_out(buf, ip);
}

/**
 * @brief 处理一个要发送的ip数据包
 * 
 * @param buf 要处理的包
 * @param ip 目标ip地址
 * @param protocol 上层协议
 */
void ip_out(buf_t *buf, uint8_t *ip, net_protocol_t protocol)
{
    // TO-DO
    // 检查从上层传递下来的数据报包长是否大于IP协议最大负载包长（1500字节（MTU） 减去IP首部长度）
    if(buf->len > ETHERNET_MAX_TRANSPORT_UNIT - sizeof(ip_hdr_t)) {
        // 分片发送
        // 最大数据长度需要取整
        int max_data_bytes = (ETHERNET_MAX_TRANSPORT_UNIT - sizeof(ip_hdr_t)) / IP_HDR_OFFSET_PER_BYTE * IP_HDR_OFFSET_PER_BYTE;
        size_t offset;
        for(offset = 0; buf->len - offset > max_data_bytes; offset += max_data_bytes) {
            buf_init(&txbuf, max_data_bytes);
            memcpy(txbuf.data, buf->data + offset, max_data_bytes);
            ip_fragment_out(&txbuf, ip, protocol, ip_id16, offset, 1);
        }
        // 最后不足一个分片
        buf_init(&txbuf, buf->len - offset);
        memcpy(txbuf.data, buf->data + offset, txbuf.len);
        ip_fragment_out(&txbuf, ip, protocol, ip_id16++, offset, 0);
    } else {
        // 直接发送
        ip_fragment_out(buf, ip, protocol, ip_id16++, 0, 0);
    }
}

/**
 * @brief 初始化ip协议
 * 
 */
void ip_init()
{
    ip_id16 = 0;
    net_add_protocol(NET_PROTOCOL_IP, ip_in);
}